package cn.seaboot.admin.mvc;

import cn.seaboot.admin.mvc.response.OrderMapper;
import cn.seaboot.commons.core.CommonUtils;
import cn.seaboot.commons.core.Converter;
import cn.seaboot.commons.exception.BizException;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.jetbrains.annotations.NotNull;

import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 * 分页控制
 * <p>
 * 设计这个类的目的是：控制全局的分页，不希望代码与 PageHelper 产生耦合，
 * 有一些项的分页可能是自己封装的，迁移我们代码的时候，会造成很多麻烦，
 * 因此需要增加这一层代码封装。
 * <p>
 * 本工具类具备拦截器的功能，对所有的分页接口有一定管理作用，可以适当增加一些鉴权相关的功能。
 * <p>
 * 关于排序的建议：
 * page-helper 的排序，格式是：[column] [desc/asc]，用的是真实数据库列名，
 * 存在一定的 SQL 注入风险，并且前端不一定能获取真实列名，因此，后台需要进行一次格式转换。
 *
 * @author Mr.css
 * @version 2022-05-20 14:41
 */
public class Pagination {

    private Pagination() {
    }

    /**
     * 默认分页容量
     */
    private static final int DEF_PAGE = 1;
    /**
     * 默认分页容量
     */
    private static final int DEF_SIZE = 10;

    /**
     * 分页起始页——参数名
     */
    private static final String KEY_PAGE = "page";
    /**
     * 分页容量——参数名
     */
    private static final String KEY_SIZE = "limit";
    /**
     * 排序字段——参数名
     */
    private static final String KEY_SORT = "sort";

    private static OrderMapper orderMapper;

    public static OrderMapper getOrderMapper() {
        return orderMapper;
    }

    public static void setOrderMapper(OrderMapper orderMapper) {
        Pagination.orderMapper = orderMapper;
    }

    /**
     * 从 request 中直接获取分页配置，自动控制分页
     *
     * @param request 请求
     * @return 分页实例
     */
    public static <E> PageInfo<E> get(HttpServletRequest request, Supplier<List<E>> supplier) {
        try (Page<E> ignored = startPage(request)) {
            return new PageInfo<>(supplier.get());
        }
    }

    /**
     * 从 request 中直接获取分页配置，自动控制分页
     *
     * @param params 请求参数
     * @return 分页实例
     */
    public static <E> PageInfo<E> get(Map<String, Object> params, Supplier<List<E>> supplier) {
        try (Page<E> ignored = startPage(params)) {
            return new PageInfo<>(supplier.get());
        }
    }

    /**
     * 从 request 中直接获取分页配置，自动控制分页
     *
     * @param request 请求
     * @return 分页实例
     */
    public static <E> PageInfo<E> apply(HttpServletRequest request, Function<Map<String, Object>, List<E>> function) {
        try (Page<E> ignored = startPage(request)) {
            Map<String, Object> params = CommonUtils.getRequestParams(request);
            return new PageInfo<>(function.apply(params));
        }
    }

    /**
     * 从 request 中直接获取分页配置，自动控制分页
     *
     * @param params 请求参数
     * @return PageInfo or List
     */
    public static <E> PageInfo<E> apply(Map<String, Object> params, Function<Map<String, Object>, List<E>> function) {
        try (Page<E> ignored = startPage(params)) {
            return new PageInfo<>(function.apply(params));
        }
    }

    /**
     * 遍历每一个分页的数据
     * <p>
     * 潜在风险：分页查询期间，数据发生变化，尤其是最后一页，会没那么准确
     *
     * @param limit    分页大小
     * @param params   参数
     * @param function 查询
     * @param consumer 处理函数
     * @param <E>      -
     */
    public static <P, E> void eachPage(int limit, P params, Function<P, List<E>> function, Consumer<List<E>> consumer) {
        int n = 1;
        try (Page<E> page = startPage(n, limit)) {
            while (true) {
                page.setPages(n);
                List<E> list = function.apply(params);
                if (CommonUtils.isEmpty(list)) {
                    // 查不到数据，提前终止
                    return;
                } else {
                    consumer.accept(list);
                    if (new PageInfo<>(list).isHasNextPage()) {
                        n++;
                    } else {
                        // 分页信息表示没有下一页
                        return;
                    }
                }
            }
        }
    }

    /**
     * 从 request 中直接获取分页配置，自动控制分页
     *
     * @param request 请求
     */
    public static <E> Page<E> startPage(HttpServletRequest request) {
        int page = Converter.toInteger(request.getParameter(KEY_PAGE), DEF_PAGE);
        int limit = Converter.toInteger(request.getParameter(KEY_SIZE), DEF_SIZE);
        return PageHelper.startPage(page, limit);
    }

    /**
     * 从 request 中直接获取分页配置，自动控制分页
     *
     * @param params 请求参数
     */
    public static <E> Page<E> startPage(Map<String, Object> params) {
        int page = Converter.toInteger(params.get(KEY_PAGE), DEF_PAGE);
        int limit = Converter.toInteger(params.get(KEY_SIZE), DEF_SIZE);
        return PageHelper.startPage(page, limit);
    }

    /**
     * 从 request 中直接获取分页配置，设置起始页
     *
     * @param request 请求
     * @param clazz   ORM 对象实体
     */
    public static <E> Page<E> startPageWithOrder(HttpServletRequest request, Class<?> clazz) {
        int page = Converter.toInteger(request.getParameter(KEY_PAGE), DEF_PAGE);
        int limit = Converter.toInteger(request.getParameter(KEY_SIZE), DEF_SIZE);
        String sort = request.getParameter(KEY_SORT);
        if (CommonUtils.isEmpty(sort)) {
            return PageHelper.startPage(page, limit);
        } else {
            Page<E> ret = PageHelper.startPage(page, limit);
            ret.setOrderBy(orderMapper.map(clazz, sort));
            return ret;
        }
    }

    /**
     * 从 request 中直接获取分页配置，设置起始页
     *
     * @param params 请求参数
     * @param clazz  分页依据
     */
    public static <E> Page<E> startPageWithOrder(Map<String, Object> params, Class<?> clazz) {
        int page = Converter.toInteger(params.get(KEY_PAGE), DEF_PAGE);
        int limit = Converter.toInteger(params.get(KEY_SIZE), DEF_SIZE);
        String sort = Converter.toString(page, KEY_SORT);
        if (CommonUtils.isEmpty(sort)) {
            return PageHelper.startPage(page, limit);
        } else {
            Page<E> ret = PageHelper.startPage(page, limit);
            ret.setOrderBy(orderMapper.map(clazz, sort));
            return ret;
        }
    }

    // camel-case end ------------------------------------------------------------------------------------------------------------------------

    /**
     * 设置分页
     *
     * @param pageNum  起始页
     * @param pageSize 页面容量
     * @param orderBy  排序
     */
    public static <E> Page<E> startPage(int pageNum, int pageSize, String orderBy) {
        return PageHelper.startPage(pageNum, pageSize, orderBy);
    }

    /**
     * 设置分页
     *
     * @param pageNum  起始页
     * @param pageSize 页面容量
     */
    public static <E> Page<E> startPage(int pageNum, int pageSize) {
        return PageHelper.startPage(pageNum, pageSize);
    }

    /**
     * 对结果集自动装包，如果存在分配配置，则使用分页，否则，直接返回集合
     *
     * @param list 查询函数
     * @param <E>  列表元素的泛型
     * @return PageInfo or List
     */
    public static <E> PageInfo<E> of(List<E> list) {
        return new PageInfo<>(list);
    }

    /**
     * 排序规则转换
     * <p>
     * 排序的格式是：[column] [desc/asc]，用的是真实数据库列名；
     * <p>
     * 这种写法，存在一定的 SQL 注入风险，而且，前端不一定能获取真实列名，因此，后台需要进行一次格式转换。
     * <p>
     * 当前函数，用户指定映射配置，根据映射配置，将字段名转换成数据库列名，安全级别较高。
     *
     * @param sort    排序字段
     * @param mapping 排序映射配置
     */
    public static void orderByMapping(@NotNull String sort, Map<String, String> mapping) {

        if (CommonUtils.isNotEmpty(sort)) {
            String[] arr = sort.split(" ");

            String v;
            StringBuilder sb = new StringBuilder();
            for (String key : arr) {
                key = key.trim();
                if (!key.isEmpty()) {
                    v = mapping.get(key);
                    if (v != null) {
                        sb.append(v).append(" ");
                    } else {
                        throw new BizException("Illegal Argument: " + key);
                    }
                }
            }
            PageHelper.orderBy(sb.toString());
        }
    }
}
